/*
 * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
 * <p>
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.chen.base.module.mybatis.config;

import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.DbType;
import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.filter.FilterEventAdapter;
import com.alibaba.druid.proxy.jdbc.JdbcParameter;
import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
import com.alibaba.druid.proxy.jdbc.StatementProxy;
import com.alibaba.druid.sql.SQLUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import java.sql.SQLException;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.List;

/**
 * 打印可执行的 sql 日志
 *
 * <p>
 * 参考：https://jfinal.com/share/2204
 * </p>
 *
 * @author ChenJinLong
 */
@Slf4j
public class DruidSqlLogFilter extends FilterEventAdapter {

	private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);

	@Value("${enable.printSql:true}")
	private Boolean enablePrintSql;
	@Value("${enable.printInfoSql:true}")
	private Boolean enablePrintInfoSql;
	@Value("${enable.printWarnSql:true}")
	private Boolean enablePrintWarnSql;

	/** statement**Before，都为sql执行之前 */
	@Override
	protected void statementExecuteBefore(StatementProxy sp, String sql) { sp.setLastExecuteStartNano(); }
	@Override
	protected void statementExecuteBatchBefore(StatementProxy sp) { sp.setLastExecuteStartNano(); }
	@Override
	protected void statementExecuteUpdateBefore(StatementProxy sp, String sql) { sp.setLastExecuteStartNano(); }
	@Override
	protected void statementExecuteQueryBefore(StatementProxy sp, String sql) { sp.setLastExecuteStartNano(); }

	/** statement**After，都为sql执行之后 */
	@Override
	protected void statementExecuteAfter(StatementProxy sp, String sql, boolean firstResult) {
		sp.setLastExecuteTimeNano();
		printFormatSql(sp);
	}
	@Override
	protected void statementExecuteBatchAfter(StatementProxy sp, int[] result) {
		sp.setLastExecuteTimeNano();
		printFormatSql(sp);
	}
	@Override
	protected void statementExecuteQueryAfter(StatementProxy sp, String sql, ResultSetProxy resultSet) {
		sp.setLastExecuteTimeNano();
		printFormatSql(sp);
	}
	@Override
	protected void statementExecuteUpdateAfter(StatementProxy sp, String sql, int updateCount) {
		sp.setLastExecuteTimeNano();
		printFormatSql(sp);
	}

	/** 关闭StatementProxy */
	@Override
	public void statement_close(FilterChain chain, StatementProxy sp) throws SQLException {
		// 先调用父类关闭 statement
		super.statement_close(chain, sp);
	}

	private void printFormatSql(StatementProxy sp) {
		// 支持动态开启
		if (!enablePrintSql) {
			return;
		}

		String sql = sp.getBatchSql();
		if (StrUtil.isNotBlank(sql)) {
			int parametersSize = sp.getParametersSize();
			List<Object> parameters = new ArrayList<>(parametersSize);
			for (int i = 0; i < parametersSize; ++i) {
				// 转换参数，处理 java8 时间
				JdbcParameter jdbcParam = sp.getParameter(i);
				if (jdbcParam == null){
					continue;
				}

				Object value = jdbcParam.getValue();
				// 处理 java8 时间
				parameters.add(value instanceof TemporalAccessor ? value.toString() : value);
			}

			String dbType = sp.getConnectionProxy().getDirectDataSource().getDbType();
			String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
			printSql(formattedSql, sp);
		}
	}

	/**
	 * 打印sql
	 */
	private void printSql(String sql, StatementProxy sp) {
		// 打印 sql
		String sqlLogger = " <Sql Time - {}>\n{}\n";

		String formatTime;
		// 纳秒
		long nanos = sp.getLastExecuteTimeNano();
		if (nanos < 1 && enablePrintInfoSql) {
			log.info(sqlLogger, "0ms", sql);
			return;
		}

		double millis = (double) nanos / (1000 * 1000);
		// 格式化执行时间，单位为 ms 和 s，保留三位小数
		if (millis > 1000) {
			formatTime = String.format("%.3fs", millis / 1000);
			// 大于2秒的，记录警告级别的慢sql日志
			if (millis >= 2000 && enablePrintWarnSql){
				log.warn(sqlLogger, formatTime, sql);
				return;
			}
		} else {
			formatTime = String.format("%.3fms", millis);
		}
		if (enablePrintInfoSql){
			log.info(sqlLogger, formatTime, sql);
		}
	}

}
